package org.iamd.domain.meta;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.iamd.domain.business.ApplicationException;
import org.iamd.domain.business.BusinessObject;
import org.iamd.domain.data.IData;
import org.iamd.domain.data.IDataAccess;
import org.iamd.util.CommonMsgKeys;
import org.iamd.util.Messenger;
import org.iamd.util.Messenger.ApplicationLocales;

/**
 * Отразява характеристиките на метаданните.
 * 
 * @author И. Атанасов
 * 
 * @param <D>
 *            VO обект
 * @param <L>
 *            Data Access обект
 */
public abstract class AMetadata<D extends IData, L extends IDataAccess<D, ? extends Object>>
		extends BusinessObject<D, L> implements Metadata {

	/**
	 * Компаратор за сортиране на наследници на NomValue. Сортирането се извършва първо по ordering,
	 * после по име на елемента.
	 * 
	 * @see Selectable#getOrdering()
	 * @see Selectable#getDisplayName()
	 * 
	 * @author И. Атанасов
	 */
	private class SelectableComparator implements Comparator<Selectable> {

		/**
		 * Сравняването се извършва първо по ordering, после по име на елемента.
		 */
		@Override
		public int compare(Selectable s1, Selectable s2) {
			int order = s1.getOrdering().compareTo(s2.getOrdering());
			return order != 0 ? order : s1.getDisplayName().compareTo(s1.getDisplayName());
		}

	}

	/** Съставни елементи. */
	private List<Selectable> children;

	/** Инстанция на класа, който сравнява номенклатурни елементи. */
	private SelectableComparator comparator;

	/** Име за пред потребителя. */
	private String displayName;

	/** <code>true</code> ако данните са линейни, <code>false</code> ако са йерархични */
	private boolean linear;

	/** Име на номенклатурата. */
	private String metaname;

	/** Клас на номенклатурен елемент. */
	private Class<? extends AMetaValue<D>> nomValClazz;

	/**
	 * Празен конструктор.
	 * 
	 * @throws ApplicationException
	 *             при неуспешно инстанциране
	 */
	protected AMetadata() throws ApplicationException {
		super();
	}

	/**
	 * Конструктор с входен параметър метаданни. Използва се при клониране.
	 * 
	 * @param nom
	 *            метаданни, които се клонират
	 * 
	 * @throws ApplicationException
	 *             при неуспешно инстанциране
	 */
	protected AMetadata(AMetadata<D, L> nom) throws ApplicationException {
		super(nom);
	}

	/**
	 * @see Metadata#getChildren()
	 */
	@Override
	public List<Selectable> getChildren() {
		return this.children;
	}

	/**
	 * @see Metadata#getDisplayName()
	 */
	@Override
	public String getDisplayName() {
		return this.displayName;
	}

	/**
	 * @see Metadata#getMetaname()
	 */
	@Override
	public String getMetaname() {
		return this.metaname;
	}

	/**
	 * @see Metadata#isLinear()
	 */
	@Override
	public boolean isLinear() {
		return this.linear;
	}

	/**
	 * @see Metadata#setChildren(List)
	 */
	@Override
	public void setChildren(List<Selectable> children) {
		this.children = children;
	}

	/**
	 * @see Metadata#setDisplayName(String)
	 */
	@Override
	public void setDisplayName(String displayName) {
		this.displayName = displayName;
	}

	/**
	 * @see Metadata#setLinear(boolean)
	 */
	@Override
	public void setLinear(boolean linear) {
		this.linear = linear;
	}

	/**
	 * @see Metadata#setMetaname(String)
	 */
	@Override
	public void setMetaname(String metaname) {
		this.metaname = metaname;
	}

	/**
	 * Клониране на метаданните.
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected AMetadata<D, L> clone() {
		try {
			List<Selectable> clonedChildren = new ArrayList<Selectable>();
			for (Selectable child : getChildren()) {
				clonedChildren.add(((AMetaValue<D>) child).clone());
			}
			Constructor<AMetadata<D, L>> constr = (Constructor<AMetadata<D, L>>) getClass()
					.getDeclaredConstructor(getClass());
			constr.setAccessible(true);
			AMetadata<D, L> result = constr.newInstance(this);
			result.setChildren(clonedChildren);
			return result;
		} catch (Exception e) {
			throw new RuntimeException(Messenger.formMessage(CommonMsgKeys.M0021_METADATA_CLONE_EX,
					new Object[] { this.metaname }, ApplicationLocales.SYSTEM_LOCALE), e);
		}
	}

	/**
	 * @return клас на номенклатурен елемент.
	 */
	protected Class<? extends AMetaValue<D>> getNomValClazz() {
		return this.nomValClazz;
	}

	/**
	 * Инициализация на инстанциалните полета.
	 * 
	 * @see org.iamd.domain.business.BusinessWorker#initialize()
	 */
	@Override
	protected void initialize() throws ApplicationException {
		super.initialize();
		this.comparator = new SelectableComparator();
		this.children = new ArrayList<Selectable>();
	}

	/**
	 * Сортира елементите на номенклатурата използвайки {@link AMetadata.SelectableComparator}
	 * 
	 * @param elems
	 *            елементи на номенклатурата
	 */
	protected void sortChildren(List<? extends Selectable> elems) {
		Collections.sort(elems, this.comparator);
		for (Selectable selectable : elems) {
			sortChildren(selectable.getChildren());
		}
	}

	/**
	 * Обекта е винаги валиден.
	 * 
	 * @see org.iamd.domain.business.BusinessObject#validate()
	 */
	@Override
	protected void validate() {
		return;
	}

}